import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_base_app/widgets/ball/ball_model.dart';
import 'package:flutter_base_app/widgets/ball/frame_call_back_manager.dart';
import 'package:sensors_plus/sensors_plus.dart';

class PhysicsBallWidget extends StatefulWidget {
  final List<Ball> ballList;
  final double height;
  final double width;
  final double badgeSize;
  final Widget Function(BuildContext context, Ball ball) builder;

  const PhysicsBallWidget({
    Key? key,
    required this.ballList,
    required this.width,
    required this.height,
    required this.builder,
    required this.badgeSize,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() => _PhysicsBallState();
}

class _PhysicsBallState extends State<PhysicsBallWidget> {
  List<Widget> badgeBallList = [];
  List<ValueKey<BadgeBallConfig>> keyList = [];
  late Size ballSize;

  @override
  void initState() {
    super.initState();
    fillKeyList();
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      FrameCallBackManager.get().addPersistentFrameCallback(travelHitMap);
    });
  }

  @override
  void dispose() {
    FrameCallBackManager.get().removePersistentFrameCallback(travelHitMap);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    fillWidgetList();
    return Stack(
      children: badgeBallList,
    );
  }

  void fillKeyList() {
    ballSize = Size(widget.badgeSize, widget.badgeSize);
    Random random = Random();
    var initOffsetX = 0.0;
    var initOffsetY = 0.0;
    var maxWidth = widget.width - 30;
    var maxHeight = widget.height - 30;

    var count = widget.ballList.length;
    var rowWidth = widget.badgeSize * count;
    var remainWidth = maxWidth - rowWidth;
    if(remainWidth > 0){
      initOffsetX = remainWidth / 2;
    }

    for (var element in widget.ballList) {
      keyList.add(ValueKey<BadgeBallConfig>(
        BadgeBallConfig()
          ..size = ballSize
          ..ball = element
          ..setInitSpeed(Speed(0, random.nextInt(300).toDouble()))
          ..setPosition(Offset(initOffsetX, initOffsetY)),
      ));
      initOffsetX = initOffsetX + widget.badgeSize + random.nextInt(10).toDouble();
      if (initOffsetX + widget.badgeSize > maxWidth) {
        initOffsetX = 0;
        initOffsetY += widget.badgeSize + random.nextInt(10).toDouble();
        if(initOffsetY + widget.badgeSize > maxHeight){
          //铺满了，不能再铺
          break;
        }
      }
    }
  }

  void fillWidgetList() {
    badgeBallList.clear();
    for (var e in keyList) {
      badgeBallList.add(
        BallItemWidget(
          key: e,
          limitWidth: widget.width,
          limitHeight: widget.height,
          builder: widget.builder,
        ),
      );
    }
  }

  void travelHitMap(Duration timeStamp) {
    for (var i = 0; i < keyList.length - 1; i++) {
      for (var j = i + 1; j < keyList.length; j++) {
        hit(keyList[i].value, keyList[j].value);
      }
    }
  }

  void hit(BadgeBallConfig a, BadgeBallConfig b) {
    final distance = a.size.height / 2 + b.size.height / 2;
    final w = b.getCurrentCenter().dx - a.getCurrentCenter().dx;
    final h = b.getCurrentCenter().dy - a.getCurrentCenter().dy;
    //中心距离
    if (sqrt(w * w + h * h) <= distance) {
      var aOriginSpeed = a.getCurrentSpeed();
      var bOriginSpeed = b.getCurrentSpeed();
      var aOffset = a.getCurrentPosition();

      var angle = atan2(h, w);
      var sinNum = sin(angle);
      var cosNum = cos(angle);

      //转换成新坐标
      var aCenter = [0.0, 0.0];
      var bCenter = coordinateTranslate(w, h, sinNum, cosNum, true);

      var aSpeed = coordinateTranslate(
          aOriginSpeed.x, aOriginSpeed.y, sinNum, cosNum, true);
      var bSpeed = coordinateTranslate(
          bOriginSpeed.x, bOriginSpeed.y, sinNum, cosNum, true);

      //速度互换？
      var vxTotal = aSpeed[0] - bSpeed[0];
      aSpeed[0] = (2 * 10 * bSpeed[0]) / 20;
      bSpeed[0] = vxTotal + aSpeed[0];

      //重叠部分
      var overlap = distance - (aCenter[0] - bCenter[0]).abs();
      aCenter[0] -= overlap;
      bCenter[0] += overlap;

      //反向算左上角
      var aRotatePos =
          coordinateTranslate(aCenter[0], aCenter[1], sinNum, cosNum, false);
      var bRotatePos =
          coordinateTranslate(bCenter[0], bCenter[1], sinNum, cosNum, false);

      var bOffsetX = aOffset.dx + bRotatePos[0];
      var bOffsetY = aOffset.dy + bRotatePos[1];
      var aOffsetX = aOffset.dx + aRotatePos[0];
      var aOffsetY = aOffset.dy + aRotatePos[1];

      var aSpeedF =
          coordinateTranslate(aSpeed[0], aSpeed[1], sinNum, cosNum, false);
      var bSpeedF =
          coordinateTranslate(bSpeed[0], bSpeed[1], sinNum, cosNum, false);

      bool aLeft = aOffsetX < bOffsetX;
      bool aTop = aOffsetY < bOffsetY;
      a.afterCollusion(
          Offset(aOffsetX, aOffsetY),
          Speed(aSpeedF[0], aSpeedF[1]),
          aLeft,
          aTop
      );
      b.afterCollusion(
          Offset(bOffsetX, bOffsetY),
          Speed(bSpeedF[0], bSpeedF[1]),
          !aLeft,
          !aTop
      );

      double aAngelSpeed = a.getAngleSpeed();
      double bAngelSpeed = b.getAngleSpeed();
      double resultAngleSpeed = 0;

      double aResultAngleSpeed = 0;
      double bResultAngleSpeed = 0;
      if(aAngelSpeed != 0 && bAngelSpeed != 0){

        if(aAngelSpeed < 0 && bAngelSpeed < 0){ //同向抵消
          resultAngleSpeed = -(aAngelSpeed.abs() - bAngelSpeed.abs()).abs() / 2;
          aResultAngleSpeed = resultAngleSpeed;
          bResultAngleSpeed = resultAngleSpeed;
        } else if(aAngelSpeed > 0 && bAngelSpeed > 0){
          resultAngleSpeed = (aAngelSpeed.abs() - bAngelSpeed.abs()).abs() / 2;
          aResultAngleSpeed = resultAngleSpeed;
          bResultAngleSpeed = resultAngleSpeed;
        } else {
          resultAngleSpeed = (aAngelSpeed.abs() + bAngelSpeed.abs()) / 2;
          aResultAngleSpeed = resultAngleSpeed;
          if(aAngelSpeed < 0){
            aResultAngleSpeed = -resultAngleSpeed;
          }
          bResultAngleSpeed = resultAngleSpeed;
          if(bAngelSpeed < 0){
            bResultAngleSpeed = -resultAngleSpeed;
          }
        }
      } else if (aAngelSpeed != 0 || bAngelSpeed != 0){
        resultAngleSpeed = aAngelSpeed + bAngelSpeed / 2;
        aResultAngleSpeed = resultAngleSpeed;
        bResultAngleSpeed = resultAngleSpeed;
      }

      a.setAngleSpeed(aResultAngleSpeed);
      b.setAngleSpeed(bResultAngleSpeed);

    }
  }

  bool isClockwise(double speedX, double speedY){
    bool result = true;
    if(speedX > 0){
      if(speedY > 0){
        result = true;
      } else {
        result = false;
      }
    } else {
      if(speedY > 0){
        result = false;
      } else {
        result = true;
      }
    }
    return result;
  }

  //坐标转换
  List<double> coordinateTranslate(
      double x, double y, double sin, double cos, bool reverse) {
    return reverse
        ? [x * cos + y * sin, y * cos - x * sin]
        : [x * cos - y * sin, y * cos + x * sin];
  }
}

class BallItemWidget extends StatefulWidget {
  final double limitWidth;
  final double limitHeight;
  final Widget Function(BuildContext context, Ball ball) builder;

  const BallItemWidget({
    Key? key,
    required this.limitWidth,
    required this.limitHeight,
    required this.builder,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() => BallItemState();
}

class BallItemState extends State<BallItemWidget> {
  final List<StreamSubscription<dynamic>> _streamSubscriptions = [];
  late BadgeBallConfig config;
  Duration sensorInterval = SensorInterval.normalInterval;

  Timer? timer;
  double x = 0;
  double y = 0;
  double angle = 0;

  double limitY = 0;
  double limitX = 0;

  @override
  void initState() {
    super.initState();
    initData();
    // 包含重力的加速度
    _streamSubscriptions.add(
      accelerometerEventStream(samplingPeriod: sensorInterval).listen(
        (AccelerometerEvent event) {
          config.setAcceleration(
            -double.parse(event.x.toStringAsFixed(1)) * 50,
            double.parse(event.y.toStringAsFixed(1)) * 50,
          );
        },
      ),
    );

    // 不包括重力的加速度
    // _streamSubscriptions.add(
    //   userAccelerometerEventStream(samplingPeriod: sensorInterval).listen(
    //     (UserAccelerometerEvent event) {
    //       config.inertiaStart(
    //         double.parse(event.x.toStringAsFixed(1)) * 50,
    //         -double.parse(event.y.toStringAsFixed(1)) * 20,
    //       );
    //     },
    //   ),
    // );

    timer = Timer.periodic(const Duration(milliseconds: 20), (timer) {
      if (!SchedulerBinding.instance.hasScheduledFrame) {
        SchedulerBinding.instance.scheduleFrame();
      }
    });

    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      FrameCallBackManager.get().addPersistentFrameCallback(updatePosition);
    });
  }

  @override
  void dispose() {
    super.dispose();
    for (var subscription in _streamSubscriptions) {
      subscription.cancel();
    }
    FrameCallBackManager.get().removePersistentFrameCallback(updatePosition);
    timer?.cancel();
    timer = null;
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedPositioned(
      left: x,
      top: y,
      duration: const Duration(milliseconds: 16),
      child: Transform.rotate(
        angle: angle,
        child: Container(
          width: config.size.width,
          alignment: Alignment.center,
          height: config.size.height,
          child: widget.builder(context, config.ball),
        ),
      ),
    );
  }

  void initData() {
    limitX = widget.limitWidth;
    limitY = widget.limitHeight;
    config = (widget.key as ValueKey<BadgeBallConfig>).value;
    config.setLimit(limitX, limitY);
    config.collusionCallback = (offset) {
      setState(() {
        // x = offset.dx;
        // y = offset.dy;
        // config.setPosition(offset);
      });
    };
    x = config.getCurrentPosition().dx;
    y = config.getCurrentPosition().dy;
  }

  void updatePosition(Duration timeStamp) {
    setState(() {
      var newOffset = config.getNextFrameOffset();
      x = newOffset.dx;
      y = newOffset.dy;
      angle = config.getAngle();
    });
  }
}
